refactor: ci/cd 오류 수정 flyway 버전 - #1448
Conversation
Walkthrough좋아요 저장 후 마일스톤에 해당할 때만 달성 이벤트를 발행합니다. 달성 이력을 중복 없이 저장하고, 대상 상태·설정·차단 여부를 검증한 뒤 알림과 푸시를 커밋 후 비동기로 처리합니다. Changes게시물 좋아요 마일스톤 알림
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant LikePostService
participant AchievementRecorder
participant NotificationProcessor
participant PushListener
LikePostService->>AchievementRecorder: 마일스톤 달성 이벤트 전달
AchievementRecorder-->>NotificationProcessor: 달성 이력 ID 전달
NotificationProcessor->>NotificationProcessor: 대상 상태와 알림 조건 검증
NotificationProcessor->>PushListener: 푸시 이벤트 발행
PushListener-->>PushListener: 수신자 조회 및 푸시 전송
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Test Results Summary184 files 184 suites 21s ⏱️ Results for commit 543b5aa. |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.java (1)
54-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win비동기와 새 트랜잭션 계약도 검증하세요.
현재 테스트는
TransactionPhase.AFTER_COMMIT만 검증합니다.@Async("asyncExecutor")또는@Transactional(propagation = Propagation.REQUIRES_NEW)가 제거되어도 테스트가 통과합니다.두 애너테이션과 설정값을 함께 검증하세요.
검증 코드 예시
+import org.springframework.scheduling.annotation.Async; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + TransactionalEventListener annotation = handleMethod.getAnnotation(TransactionalEventListener.class); + Async asyncAnnotation = handleMethod.getAnnotation(Async.class); + Transactional transactionalAnnotation = handleMethod.getAnnotation(Transactional.class); assertThat(annotation).isNotNull(); assertThat(annotation.phase()).isEqualTo(TransactionPhase.AFTER_COMMIT); + assertThat(asyncAnnotation).isNotNull(); + assertThat(asyncAnnotation.value()).isEqualTo("asyncExecutor"); + assertThat(transactionalAnnotation).isNotNull(); + assertThat(transactionalAnnotation.propagation()).isEqualTo(Propagation.REQUIRES_NEW);As per path instructions, "테스트 케이스가 충분한지 확인합니다 (성공/실패/경계 케이스)."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.java` around lines 54 - 64, Update handle_shouldRunAfterCommit in PostLikeMilestonePushListenerTest to also retrieve and assert the listener method’s `@Async` annotation value is "asyncExecutor" and its `@Transactional` annotation propagation is Propagation.REQUIRES_NEW, while preserving the existing AFTER_COMMIT assertion.Source: Path instructions
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.java (1)
21-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win비동기 리스너에서 예외를 로깅해 주세요.
handle은@Async의void메서드입니다. 예외가 발생하면 호출 스택으로 전파되지 않고AsyncUncaughtExceptionHandler로 넘어갑니다. 전역 핸들러가 없으면 실패 원인이 남지 않습니다. 좋아요 트랜잭션은 이미 커밋된 상태이므로 사용자 요청에도 오류가 드러나지 않습니다.SLF4J로 실패를 기록해 주세요. 이력 ID와 게시글 ID를 남기면 재처리 시 추적이 쉬워집니다.
♻️ 제안 변경
+import lombok.extern.slf4j.Slf4j; + `@Component` +@Slf4j `@RequiredArgsConstructor` public class PostLikeMilestoneReachedListener { private final PostLikeMilestoneAchievementRecorder achievementRecorder; private final PostLikeMilestoneNotificationProcessor notificationProcessor; `@Async`("asyncExecutor") `@TransactionalEventListener`(phase = TransactionPhase.AFTER_COMMIT) public void handle(PostLikeMilestoneReachedEvent event) { - achievementRecorder.record(event) - .ifPresent(notificationProcessor::process); + try { + achievementRecorder.record(event) + .ifPresent(notificationProcessor::process); + } catch (Exception exception) { + log.error("게시글 좋아요 마일스톤 알림 처리 실패. postId={}, milestoneCount={}", + event.postId(), event.milestoneCount(), exception); + } + }민감 정보를 남기지 않도록
likerId는 로그에서 제외했습니다.As per path instructions: "로깅은 SLF4J를 사용하고 System.out.println은 절대 사용하지 않습니다."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.java` around lines 21 - 26, Update PostLikeMilestoneReachedListener.handle to catch exceptions from the asynchronous achievementRecorder.record/notificationProcessor flow and log them through an SLF4J logger. Include the history ID and post ID in the failure log for traceability, exclude likerId, and preserve the existing processing behavior.Source: Path instructions
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java (1)
35-40: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy liftPENDING 상태로 남은 이력의 복구 경로를 마련해 주세요(운영 조언).
process가 중간에 예외로 실패하면 이력은PENDING으로 남습니다. 현재 흐름에는 재시도 주체가 없어서 해당 마일스톤 알림은 영구 누락됩니다.
PENDING상태와 생성 시각을 기준으로 재처리하는 배치 잡을 추가하면 좋습니다.process는 이미 상태 검사로 멱등성을 확보하므로 배치 재실행에 안전합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java` around lines 35 - 40, PostLikeMilestoneNotificationProcessor의 process에서 예외로 PENDING에 남은 이력을 복구할 재처리 배치를 추가하세요. PENDING 상태이면서 생성 시각이 기준 시간을 지난 PostLikeMilestoneAchievement를 조회해 각 achievementId로 process를 호출하고, 기존 process의 상태 검사와 멱등성을 그대로 활용하세요.app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.java (1)
99-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win저장되는 알림 본문도 검증하면 좋겠습니다.
현재
save(any(Notification.class))만 검증하므로 서비스 알림의 제목·본문 회귀를 잡지 못합니다. 푸시 문구는 이벤트로 검증되지만 서비스 알림 문구는 검증 공백입니다.
ArgumentCaptor로 저장된Notification을 포착해 문구를 확인해 주세요.♻️ 제안 변경
InOrder inOrder = inOrder(notificationWriter, achievementWriter, eventPublisher); - inOrder.verify(notificationWriter).save(any(Notification.class)); + ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class); + inOrder.verify(notificationWriter).save(notificationCaptor.capture()); + assertThat(notificationCaptor.getValue().getTitle()) + .isEqualTo(String.format("게시물이 좋아요 %d개를 달성했습니다!", milestoneCount)); inOrder.verify(notificationWriter).saveLog(postWriter, notification);
org.mockito.ArgumentCaptor임포트가 필요합니다.Notification의 실제 접근자 이름에 맞춰 조정해 주세요.As per path instructions: "테스트 케이스가 충분한지 확인합니다 (성공/실패/경계 케이스)."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.java` around lines 99 - 104, Update the notification verification in PostLikeMilestoneNotificationProcessorTest to capture the Notification passed to notificationWriter.save using ArgumentCaptor, then assert its title and body match the expected service-notification text. Preserve the existing InOrder verification and event assertions, and add coverage for the relevant success/failure/boundary cases only if needed to validate the message behavior.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java`:
- Around line 31-36: PostLikeMilestoneAchievement 엔티티 변경에 대응하는 Flyway 마이그레이션을
추가하세요. tb_post_like_milestone_achievement 테이블과 post_id, trigger_user_id,
notification_id 외래 키, uk_post_like_milestone_achievement_post_milestone 유니크 제약,
idx_post_like_milestone_achievement_trigger_user 인덱스를 생성하고, PR에 db-change 라벨을
지정하세요.
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`:
- Around line 17-20: Remove the class-level `@Transactional` from
PostLikeMilestoneAchievementWriter and keep this writer focused on persistence
state changes. Move the transaction boundary to the appropriate Service method
or explicit use-case entry point that coordinates the milestone achievement
flow, using method-level `@Transactional` there.
- Around line 24-36: Make savePendingIfAbsent atomically handle duplicate
post_id and milestone_count inserts by catching DataIntegrityViolationException,
returning Optional.empty() only when it originates from the
PostLikeMilestoneAchievement unique constraint, and rethrowing all other
integrity violations; preserve successful saves so
notificationProcessor::process can run. Add the required Flyway migration for
this unique constraint. In
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java:24-36
update PostLikeMilestoneAchievementWriter; in
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java:36-65
add a `@DataJpaTest` parallel-save case proving only one call succeeds for the
same post and milestone.
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListener.java`:
- Around line 24-33: Update PostLikeMilestonePushListener.handle to catch
push-delivery failures from NotificationPushSender and persist or enqueue a
durable compensation/retry task for the associated PostLikeMilestoneAchievement,
allowing failed notifications to be resent. Preserve the existing recipient
lookup and successful send flow, and ensure compensation covers failures
propagated from PostLikeMilestoneNotificationProcessor.
---
Nitpick comments:
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java`:
- Around line 35-40: PostLikeMilestoneNotificationProcessor의 process에서 예외로
PENDING에 남은 이력을 복구할 재처리 배치를 추가하세요. PENDING 상태이면서 생성 시각이 기준 시간을 지난
PostLikeMilestoneAchievement를 조회해 각 achievementId로 process를 호출하고, 기존 process의 상태
검사와 멱등성을 그대로 활용하세요.
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.java`:
- Around line 21-26: Update PostLikeMilestoneReachedListener.handle to catch
exceptions from the asynchronous
achievementRecorder.record/notificationProcessor flow and log them through an
SLF4J logger. Include the history ID and post ID in the failure log for
traceability, exclude likerId, and preserve the existing processing behavior.
In
`@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.java`:
- Around line 99-104: Update the notification verification in
PostLikeMilestoneNotificationProcessorTest to capture the Notification passed to
notificationWriter.save using ArgumentCaptor, then assert its title and body
match the expected service-notification text. Preserve the existing InOrder
verification and event assertions, and add coverage for the relevant
success/failure/boundary cases only if needed to validate the message behavior.
In
`@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.java`:
- Around line 54-64: Update handle_shouldRunAfterCommit in
PostLikeMilestonePushListenerTest to also retrieve and assert the listener
method’s `@Async` annotation value is "asyncExecutor" and its `@Transactional`
annotation propagation is Propagation.REQUIRES_NEW, while preserving the
existing AFTER_COMMIT assertion.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3acaaac0-cf6d-4719-8414-c4c5d3fdf4e7
📒 Files selected for processing (28)
app-main/src/main/java/net/causw/app/main/domain/community/post/service/LikePostService.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/enums/PostLikeMilestoneAchievementStatus.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/enums/PostLikeMilestoneSuppressionReason.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikeMilestonePushEvent.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikeMilestoneReachedEvent.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikedEvent.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/repository/PostLikeMilestoneAchievementRepository.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementReader.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementRecorder.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListener.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListener.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.javaapp-main/src/main/java/net/causw/app/main/domain/notification/notification/service/policy/LikePostNotificationPolicy.javaapp-main/src/main/java/net/causw/app/main/shared/exception/errorcode/PostLikeMilestoneAchievementErrorCode.javaapp-main/src/test/java/net/causw/app/main/SchemaValidationTest.javaapp-main/src/test/java/net/causw/app/main/domain/community/post/service/LikePostServiceTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievementTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementReaderTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementRecorderTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListenerTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListenerTest.javaapp-main/src/test/java/net/causw/app/main/domain/notification/notification/service/policy/LikePostNotificationPolicyTest.java
💤 Files with no reviewable changes (3)
- app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListener.java
- app-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikedEvent.java
- app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListenerTest.java
| @Table(name = "tb_post_like_milestone_achievement", uniqueConstraints = { | ||
| @UniqueConstraint(name = "uk_post_like_milestone_achievement_post_milestone", columnNames = {"post_id", | ||
| "milestone_count"}) | ||
| }, indexes = { | ||
| @Index(name = "idx_post_like_milestone_achievement_trigger_user", columnList = "trigger_user_id") | ||
| }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== Flyway SQL files =="
fd -t f -e sql . | sort -V
echo "== Achievement schema references =="
fd -t f -e sql . -x rg -n -i -C 3 \
'post_like_milestone_achievement|uk_post_like_milestone_achievement_post_milestone|idx_post_like_milestone_achievement_trigger_user' {}
echo "== Current PR labels, if available =="
if gh pr view --json labels --jq '.labels[].name' 2>/dev/null; then
:
else
echo "PR labels could not be read in this environment."
fiRepository: CAUCSE/CAUSW_backend
Length of output: 8299
Flyway 스키마 변경을 함께 추가해 주세요.
tb_post_like_milestone_achievement 테이블, 외래 키(post_id, trigger_user_id, notification_id), uk_post_like_milestone_achievement_post_milestone, idx_post_like_milestone_achievement_trigger_user 변경이 엔티티에 추가되어 있으므로, 이 PR에 VYYYYMMDDHHMMSS__create_post_like_milestone_achievement.sql Flyway 마이그레이션과 db-change 라벨을 포함해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java`
around lines 31 - 36, PostLikeMilestoneAchievement 엔티티 변경에 대응하는 Flyway 마이그레이션을
추가하세요. tb_post_like_milestone_achievement 테이블과 post_id, trigger_user_id,
notification_id 외래 키, uk_post_like_milestone_achievement_post_milestone 유니크 제약,
idx_post_like_milestone_achievement_trigger_user 인덱스를 생성하고, PR에 db-change 라벨을
지정하세요.
Source: Path instructions
| @Component | ||
| @RequiredArgsConstructor | ||
| @Transactional | ||
| public class PostLikeMilestoneAchievementWriter { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
트랜잭션 경계를 Service 메서드로 이동하세요.
PostLikeMilestoneAchievementWriter는 Implementation 계층입니다. Line 19의 클래스 수준 @Transactional은 Writer의 모든 public 메서드를 트랜잭션 진입점으로 만듭니다.
유스케이스를 조합하는 Service 메서드 또는 명시적인 유스케이스 진입점에 트랜잭션 경계를 두세요. Writer는 영속 상태 변경만 수행하게 유지하세요.
As per path instructions, "트랜잭션 경계(@Transactional)가 Service 메서드 단위로 적절하게 설정되어 있는지 확인합니다."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`
around lines 17 - 20, Remove the class-level `@Transactional` from
PostLikeMilestoneAchievementWriter and keep this writer focused on persistence
state changes. Move the transaction boundary to the appropriate Service method
or explicit use-case entry point that coordinates the milestone achievement
flow, using method-level `@Transactional` there.
Source: Path instructions
| public Optional<PostLikeMilestoneAchievement> savePendingIfAbsent( | ||
| Post post, | ||
| User triggerUser, | ||
| long milestoneCount) { | ||
| if (achievementRepository.existsByPostIdAndMilestoneCount(post.getId(), milestoneCount)) { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| PostLikeMilestoneAchievement achievement = PostLikeMilestoneAchievement.pending( | ||
| post, | ||
| triggerUser, | ||
| milestoneCount); | ||
| return Optional.of(achievementRepository.saveAndFlush(achievement)); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline \
app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java \
--items all
fd -t f -0 'V.*__.*\.sql$' . |
xargs -0 -r rg -n -C 4 \
'PostLikeMilestoneAchievement|post_id|milestone_count|UNIQUE|unique'
rg -n -C 5 \
'uniqueConstraints|post_id|milestoneCount|milestone_count' \
app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.javaRepository: CAUCSE/CAUSW_backend
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## Candidate files"
fd -t f \
'PostLikeMilestoneAchievementWriter.java|PostLikeMilestoneAchievementTest.java|PostLikeMilestoneAchievement.java|PostLikeMilestoneAchievementRepository.java|.*Notification.*Service.*\.java|.*Application.*Listener.*\.java|.*Event.*\.java' \
app-main/src/main/java app-main/src/test/java
echo
echo "## Writer"
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java
echo
echo "## Entity"
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java
echo
echo "## Repository"
fd -t f 'PostLikeMilestoneAchievementRepository.java' app-main/src/main/java -x cat -n {}
echo
echo "## Test"
cat -n app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java
echo
echo "## Migrations mentioning entity/table names"
rg -n -C 4 'PostLikeMilestoneAchievement|post_like_milestone|milestone_count|milestone_count|trigger_user_id|status' app-main/src/main/resources/db/migration app-main/src/main/java/net/causw/app/main/domain/notification || true
echo
echo "## Call sites"
rg -n -C 4 'savePendingIfAbsent|PostLikeMilestoneAchievementWriter|PostLikeMilestoneAchievement' app-main/src/main/java app-main/src/test/javaRepository: CAUCSE/CAUSW_backend
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## PostLikeMilestoneAchievement call sites"
rg -n -C 5 'savePendingIfAbsent|PostLikeMilestoneAchievementWriter|PostLikeMilestoneReachedEvent|PostLikeMilestoneReachedListener|PostLikeMilestoneNotificationProcessor|transactionalEventListener' \
app-main/src/main/java/net/causw/app/main/domain/notification app-main/src/test/java
echo
echo "## Writer usages in repositories/services via direct grep across app-main"
rg -n -C 4 'PostLikeMilestoneAchievementWriter|savePendingIfAbsent' app-main/src/main/java app-main/src/test/java || trueRepository: CAUCSE/CAUSW_backend
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## Conflict handling symbols"
rg -n -C 3 'DataIntegrityViolationException|isPostMilestoneUniqueConstraintViolation|PostLikeMilestoneAchievementErrorCode|PostLikeMilestoneAchievement' \
app-main/src/main/java/net/causw/app/main/domain/notification | head -n 220
echo
echo "## Focused constraint check"
python3 - <<'PY'
from pathlib import Path
target = "tb_post_like_milestone_achievement"
for path in Path("app-main/src/main/resources/db/migration").glob("*.sql"):
txt = path.read_text(errors="ignore").lower()
if target in txt:
lines = txt.splitlines()
for i, line in enumerate(lines, 1):
if target in line or "uniqu" in line or "constraint" in line:
print(f"{path}:{i}:{line.strip()}")
PYRepository: CAUCSE/CAUSW_backend
Length of output: 32447
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## ErrorCodes related to milestone achievement"
fd -t f 'PostLikeMilestoneAchievementErrorCode.java|.*ErrorCode.*java' app-main/src/main/java/net/causw/app/main/domain -x sh -c 'echo "--- $1"; rg -n -C 3 "POST_LIKE_MILESTONE_ACHIEVEMENT|milestone|Unique|UNIQUE|unique" "$1" || true' sh {}
echo
echo "## Base exception / runtime exception handling"
rg -n -C 3 'BaseException|BaseRunTimeV2Exception|RuntimeException|`@ExceptionHandler`|DataIntegrityViolationException|Postlike' app-main/src/main/java/net/causw/app/main/shared app-main/src/main/java/net/causw/app/main/domain/notification | head -n 260Repository: CAUCSE/CAUSW_backend
Length of output: 27673
마일스톤 중복 방지 로직을 원자적으로 처리하세요.
savePendingIfAbsent()는 existsByPostIdAndMilestoneCount()와 saveAndFlush()가 별도 작업이라, 같은 게시글과 마일스톤 이벤트가 동시에 처리되면 두 요청 모두 저장합니다. savePendingIfAbsent()의 충돌 상황을 Optional.empty()로 처리하고, 충돌이 없으면 notificationProcessor::process가 실행될 수 있습니다.
현재 PostLikeMilestoneAchievement 엔티티는 (post_id, milestone_count)에 JPA UniqueConstraint가 있지만, 이 제약에 대한 Flyway 생성迁移와 충돌 오류를 처리하는 예외 처리는 없습니다. DataIntegrityViolationException을 필터해서 해당 제약 only Optional.empty() 처리하고, 다른 무결성 오류는 그대로 전파하세요.
또한 PostLikeMilestoneAchievementWriterTest는 Mockito 단위 케이스만覆蓋하므로, post_id + milestone_count 동시 저장 시 DB가 중복을 막고 한 호출만 처리되는지 확인하는 @DataJpaTest 병렬 케이스를 추가하세요.
📍 Affects 2 files
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java#L24-L36(this comment)app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java#L36-L65
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`
around lines 24 - 36, Make savePendingIfAbsent atomically handle duplicate
post_id and milestone_count inserts by catching DataIntegrityViolationException,
returning Optional.empty() only when it originates from the
PostLikeMilestoneAchievement unique constraint, and rethrowing all other
integrity violations; preserve successful saves so
notificationProcessor::process can run. Add the required Flyway migration for
this unique constraint. In
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java:24-36
update PostLikeMilestoneAchievementWriter; in
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java:36-65
add a `@DataJpaTest` parallel-save case proving only one call succeeds for the
same post and milestone.
Source: Path instructions
| @Async("asyncExecutor") | ||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void handle(PostLikeMilestonePushEvent event) { | ||
| User recipient = userReader.findUserById(event.recipientUserId()); | ||
| notificationPushSender.sendToUser( | ||
| recipient, | ||
| event.pushTitle(), | ||
| event.pushBody(), | ||
| event.pushData()); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline \
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/NotificationPushSender.java \
--items all
rg -n -C 8 \
'\bsend(ToUser)?\s*\(|timeout|retry|Retry|catch\s*\(|throw\s+' \
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/NotificationPushSender.java
rg -n -C 6 \
'markNotificationCreated|PostLikeMilestonePushEvent|NOTIFICATION_CREATED' \
app-main/src/main/javaRepository: CAUCSE/CAUSW_backend
Length of output: 20617
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== PushNotificationSender candidates =="
fd -i 'PushNotificationSender' app-main/src/main/java || true
echo
echo "== NotificationPushSender full =="
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/NotificationPushSender.java
echo
echo "== PushNotificationSender implementations/usages =="
rg -n -C 10 'interface\s+PushNotificationSender|class\s+\w+\s+implements\s+PushNotificationSender|`@Bean`|PushNotificationSender|\.send\(' app-main/src/main/java/net/causw/app/main/shared app-main/src/main/java/net/causw/app/main/domain || true
echo
echo "== Async/timeout/thread config references =="
rg -n -C 4 '`@EnableAsync`|AsyncConfigurer|TaskExecutor|asyncExecutor|`@Timeout`|Timeout|setWaitTimeAfterSubmit|maxPoolSize|queueCapacity' app-main/src/main/java | head -n 200 || trueRepository: CAUCSE/CAUSW_backend
Length of output: 50376
푸시 실패 보상 처리를 추가하세요.
PostLikeMilestoneAchievement는 푸시 이벤트 발행 전에 NOTIFICATION_CREATED 상태로 저장됩니다. NotificationPushSender는 FirebaseMessagingException을 토큰 제거와 로그로 막지만, 일반 예외는 로그만 남기고 재시도나 보상을 제공하지 않습니다. PostLikeMilestoneNotificationProcessor에서 푸시 발행 실패가 발생할 수 있으므로 실패 알림을 내구성 있는 작업으로 재보낼 수 있도록 처리하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListener.java`
around lines 24 - 33, Update PostLikeMilestonePushListener.handle to catch
push-delivery failures from NotificationPushSender and persist or enqueue a
durable compensation/retry task for the associated PostLikeMilestoneAchievement,
allowing failed notifications to be resent. Preserve the existing recipient
lookup and successful send flow, and ensure compensation covers failures
propagated from PostLikeMilestoneNotificationProcessor.
Source: Path instructions
🚩 관련사항
dev 배포 수정
📢 전달사항
flyway 버전 순서로 인한 문제
새 스크립트 버전을 올렸습니다.
📸 스크린샷
관련한 스크린샷을 첨부해주세요.
📃 진행사항
⚙️ 기타사항
기타 참고사항을 적어주세요.
개발기간:
Summary by CodeRabbit
새로운 기능
변경 사항